package actor

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"

	mydebug "gitee.com/nggs/debug"
	"gitee.com/nggs/log"
	"gitee.com/nggs/protoactor-go/actor"
)

type ISuper interface {
	WithOnReceiveMessageHandler(vf VFOnReceiveMessage) ISuper
	WithOnStartedHandler(vf VFOnStarted) ISuper
	WithOnStoppingHandler(vf VFOnStopping) ISuper
	WithOnStoppedHandler(vf VFOnStopped) ISuper
	WithOnActorTerminateHandler(vf VFOnActorTerminated) ISuper
	WithOnRestartingHandler(vf VFOnRestarting) ISuper
	WithOnRestartedHandler(vf VFOnRestarted) ISuper
	WithOnTimeoutHandler(vf VFOnTimeout) ISuper

	PID() *actor.PID
	ParentPID() *actor.PID

	Sign() string
	SetSign(sign string)

	Logger() log.ILogger
	SetLogger(logger log.ILogger)

	Start(ctx actor.Context, name string) error
	StartWithPrefix(ctx actor.Context, prefix string) error
	WaitForStarted()
	IsStopping() bool
	Stop() error
	WaitForStopped()

	NewTimer(dur time.Duration, cb TimerCallback) TimerID
	NewTimerWithTag(dur time.Duration, tag TimerID, cb TimerCallback) TimerID
	NewLoopTimer(interval time.Duration, cb TimerCallback) TimerID
	NewLoopTimerWithTag(dur time.Duration, tag TimerID, cb TimerCallback) TimerID
	StopTimer(id TimerID) error

	NewHttpClientTask(timeout time.Duration, prepare HttpClientTaskPrepare, callback HttpClientTaskCallback) (taskID HttpClientTaskID, err error)

	Debug(format string, args ...interface{})
	Info(format string, args ...interface{})
	Warn(format string, args ...interface{})
	Error(format string, args ...interface{})

	Receive(ctx actor.Context)
}

func decider(reason interface{}) actor.Directive {
	Logger.Error("crashed, reason: %#v\nstack: %s", reason, mydebug.Stack())
	return actor.StopDirective
}

var RootContext = actor.EmptyRootContext

type VFOnStarted func(ctx actor.Context)
type VFOnStopping func(ctx actor.Context)
type VFOnStopped func(ctx actor.Context)
type VFOnReceiveMessage func(ctx actor.Context)
type VFOnActorTerminated func(who *actor.PID, ctx actor.Context)
type VFOnRestarting func(ctx actor.Context)
type VFOnRestarted func(ctx actor.Context)
type VFOnTimeout func(ctx actor.Context, id TimerID, tag TimerID)

type State = int32

const (
	INIT = iota
	STARTED
	STOPPING
	STOPPED
	RESTARTING
	RESTARTED
)

type Super struct {
	vfOnStarted         VFOnStarted
	vfOnStopping        VFOnStopping
	vfOnStopped         VFOnStopped
	vfOnReceiveMessage  VFOnReceiveMessage
	vfOnActorTerminated VFOnActorTerminated
	vfOnRestarting      VFOnRestarting
	vfOnRestarted       VFOnRestarted
	vfOnTimeout         VFOnTimeout

	sign string

	logger log.ILogger

	pid       *actor.PID
	parentPID *actor.PID

	timerMgr          *TimerManager
	httpClientTaskMgr *HttpClientTaskManager

	state State

	startedWg     *sync.WaitGroup
	stoppedWg     *sync.WaitGroup
	stoppedWgOnce sync.Once
	stopFlag      int32
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func NewSuper(logger log.ILogger, startedWg *sync.WaitGroup, stoppedWg *sync.WaitGroup) ISuper {
	s := &Super{
		logger:    logger,
		startedWg: startedWg,
		stoppedWg: stoppedWg,
		state:     INIT,
	}

	if s.logger == nil {
		s.logger = Logger
	}

	if s.startedWg != nil {
		s.startedWg.Add(1)
	}

	s.vfOnReceiveMessage = func(ctx actor.Context) {}
	s.vfOnStarted = func(ctx actor.Context) {}
	s.vfOnStopping = func(ctx actor.Context) {}
	s.vfOnStopped = func(ctx actor.Context) {}
	s.vfOnActorTerminated = func(who *actor.PID, ctx actor.Context) {}
	s.vfOnRestarting = func(ctx actor.Context) {}
	s.vfOnRestarted = func(ctx actor.Context) {}
	s.vfOnTimeout = func(ctx actor.Context, id TimerID, tag TimerID) {}

	return s
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func (s *Super) WithOnReceiveMessageHandler(vf VFOnReceiveMessage) ISuper {
	if vf != nil {
		s.vfOnReceiveMessage = vf
	}
	return s
}

func (s *Super) WithOnStartedHandler(vf VFOnStarted) ISuper {
	if vf != nil {
		s.vfOnStarted = vf
	}
	return s
}

func (s *Super) WithOnStoppingHandler(vf VFOnStopping) ISuper {
	if vf != nil {
		s.vfOnStopping = vf
	}
	return s
}

func (s *Super) WithOnStoppedHandler(vf VFOnStopped) ISuper {
	if vf != nil {
		s.vfOnStopped = vf
	}
	return s
}

func (s *Super) WithOnActorTerminateHandler(vf VFOnActorTerminated) ISuper {
	if vf != nil {
		s.vfOnActorTerminated = vf
	}
	return s
}

func (s *Super) WithOnRestartingHandler(vf VFOnRestarting) ISuper {
	if vf != nil {
		s.vfOnRestarting = vf
	}
	return s
}

func (s *Super) WithOnRestartedHandler(vf VFOnRestarted) ISuper {
	if vf != nil {
		s.vfOnRestarted = vf
	}
	return s
}

func (s *Super) WithOnTimeoutHandler(vf VFOnTimeout) ISuper {
	if vf != nil {
		s.vfOnTimeout = vf
	}
	return s
}

func (s Super) PID() *actor.PID {
	return s.pid
}

func (s Super) ParentPID() *actor.PID {
	return s.parentPID
}

func (s Super) Sign() string {
	return s.sign
}

func (s *Super) SetSign(sign string) {
	s.sign = sign
}

func (s Super) Logger() log.ILogger {
	return s.logger
}

func (s *Super) SetLogger(logger log.ILogger) {
	if logger != nil {
		s.logger = logger
	}
}

func (s Super) State() State {
	return s.state
}

func (s *Super) Start(ctx actor.Context, name string) (err error) {
	props := actor.PropsFromProducer(func() actor.Actor { return s }).
		WithSupervisor(actor.NewOneForOneStrategy(0, 0, decider))

	if ctx != nil {
		s.pid, err = ctx.SpawnNamed(props, name)
		if err != nil {
			return
		}
	} else {
		s.pid, err = RootContext.SpawnNamed(props, name)
		if err != nil {
			return
		}
	}

	s.sign = s.pid.Id

	return
}

func (s *Super) StartWithPrefix(ctx actor.Context, prefix string) (err error) {
	props := actor.PropsFromProducer(func() actor.Actor { return s }).
		WithSupervisor(actor.NewOneForOneStrategy(0, 0, decider))

	if ctx != nil {
		s.pid = ctx.SpawnPrefix(props, prefix)
	} else {
		s.pid = RootContext.SpawnPrefix(props, prefix)
	}

	s.sign = s.pid.Id

	return
}

func (s *Super) WaitForStarted() {
	if s.startedWg != nil {
		s.startedWg.Wait()
	}
}

func (s Super) IsStopping() bool {
	return atomic.LoadInt32(&s.stopFlag) == 1
}

func (s *Super) Stop() error {
	if !atomic.CompareAndSwapInt32(&s.stopFlag, 0, 1) {
		return &ErrAlreadyStop{Sign: s.sign}
	}
	if s.pid != nil {
		RootContext.Stop(s.pid)
	}
	return nil
}

func (s *Super) WaitForStopped() {
	if s.stoppedWg != nil {
		s.stoppedWg.Wait()
	}
}

func (s *Super) NewTimer(dur time.Duration, cb TimerCallback) TimerID {
	return s.timerMgr.NewTimer(dur, cb)
}

func (s *Super) NewTimerWithTag(dur time.Duration, tag TimerID, cb TimerCallback) TimerID {
	return s.timerMgr.NewTimerWithTag(dur, tag, cb)
}

func (s *Super) NewLoopTimer(interval time.Duration, cb TimerCallback) TimerID {
	return s.timerMgr.NewLoopTimer(interval, cb)
}

func (s *Super) NewLoopTimerWithTag(dur time.Duration, tag TimerID, cb TimerCallback) TimerID {
	return s.timerMgr.NewLoopTimerWithTag(dur, tag, cb)
}

func (s *Super) StopTimer(id TimerID) error {
	return s.timerMgr.Stop(id)
}

func (s *Super) NewHttpClientTask(timeout time.Duration, prepare HttpClientTaskPrepare, callback HttpClientTaskCallback) (taskID HttpClientTaskID, err error) {
	return s.httpClientTaskMgr.NewTask(timeout, prepare, callback)
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func (s *Super) Debug(format string, args ...interface{}) {
	s.logger.Debug("[%s] %s", s.Sign(), fmt.Sprintf(format, args...))
}

func (s *Super) Info(format string, args ...interface{}) {
	s.logger.Info("[%s] %s", s.Sign(), fmt.Sprintf(format, args...))
}

func (s *Super) Warn(format string, args ...interface{}) {
	s.logger.Warn("[%s] %s", s.Sign(), fmt.Sprintf(format, args...))
}

func (s *Super) Error(format string, args ...interface{}) {
	s.logger.Error("[%s] %s", s.Sign(), fmt.Sprintf(format, args...))
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func (s *Super) Receive(ctx actor.Context) {
	switch msg := ctx.Message().(type) {
	case *Timeout:
		if err := s.timerMgr.Trigger(msg.ID); err != nil {
			s.Error("trigger time fail, %s", err)
		} else {
			s.vfOnTimeout(ctx, msg.ID, msg.Tag)
		}

	case *HttpClientTaskComplete:
		if err := s.httpClientTaskMgr.OnComplete(msg.ID); err != nil {
			s.Error("do http client task fail, %s", err)
		}

	case *actor.Started:
		switch s.state {
		case INIT:
			s.pid = ctx.Self()
			s.parentPID = ctx.Parent()
			s.timerMgr = NewTimerManager(s.pid)
			s.httpClientTaskMgr = NewHttpClientTaskManager(s.pid)
			s.state = STARTED
			s.vfOnStarted(ctx)
			if s.startedWg != nil {
				s.startedWg.Done()
			}
			if s.stoppedWg != nil {
				s.stoppedWg.Add(1)
			}

		case RESTARTING:
			s.state = RESTARTED
			s.vfOnRestarted(ctx)

		default:
			s.Error("receive *Started in state[%d]", s.state)
		}

	case *actor.Stopping:
		atomic.CompareAndSwapInt32(&s.stopFlag, 0, 1)
		s.state = STOPPING
		s.vfOnStopping(ctx)

	case *actor.Stopped:
		s.state = STOPPED
		s.vfOnStopped(ctx)
		s.timerMgr.StopAll()
		if s.stoppedWg != nil {
			s.stoppedWgOnce.Do(func() {
				// 避免重复Done, 导致panic
				s.stoppedWg.Done()
			})
		}

	case *actor.Restarting:
		s.Error("restarting")
		s.state = RESTARTING
		s.vfOnRestarting(ctx)

	case *actor.Terminated:
		s.vfOnActorTerminated(msg.Who, ctx)

	default:
		s.vfOnReceiveMessage(ctx)
	}
}
